﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using StateMachine;
using System.Globalization;
using System.ComponentModel;

namespace LilTmSim
{
	/// <summary>
	/// Interaction logic for TransitionNode.xaml
	/// </summary>
	public partial class TransitionNode : UserControl, INode, INotifyPropertyChanged, IRenameValidator
	{
		public delegate void SenderOnly(object sender);
		public event SenderOnly OnTransitionDeleteWanted;
		public event SenderOnly OnTextChanged;

		/// <summary>
		/// TransitionData is a Dependency Property.on
		/// </summary>
		public Transition TransitionData
		{
			get { return (Transition)GetValue(TransitionDataProperty); }
			set { SetValue(TransitionDataProperty, value); }
		}
		public static readonly DependencyProperty TransitionDataProperty =
			DependencyProperty.Register("TransitionData", typeof(Transition), typeof(TransitionNode), new UIPropertyMetadata(null));


		/// <summary>
		/// StartPoint is a Dependency Property.
		/// </summary>
		public Point StartPoint
		{
			get { return (Point)GetValue(StartPointProperty); }
			set { SetValue(StartPointProperty, value); }
		}
		public static readonly DependencyProperty StartPointProperty =
			DependencyProperty.Register("StartPoint", typeof(Point), typeof(TransitionNode), new UIPropertyMetadata(new Point(), new PropertyChangedCallback(StartPointChanged)));

		static void StartPointChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			TransitionNode t = ((TransitionNode)o);
			if (t.StartStateNode != null && t.EndStateNode != null)
			{
				Point midPoint = t.GetMiddlePoint(t.EndStateNode.Location, t.StartStateNode.OldLocation);
				Point offset = t.PointOffset(t.MiddlePoint, midPoint);
				t.RepositionMiddlePoint(offset);
			}

			t.InvalidateVisual();
		}
		
		/// <summary>
		/// EndPoint is a Dependency Property.
		/// </summary>
		public Point EndPoint
		{
			get { return (Point)GetValue(EndPointProperty); }
			set { SetValue(EndPointProperty, value); }
		}
		public static readonly DependencyProperty EndPointProperty =
			DependencyProperty.Register("EndPoint", typeof(Point), typeof(TransitionNode), new UIPropertyMetadata(new Point(), new PropertyChangedCallback(EndPointChanged)));

		static void EndPointChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			TransitionNode t = ((TransitionNode)o);
			if (t.StartStateNode != null && t.EndStateNode != null)
			{
				Point midPoint = t.GetMiddlePoint(t.StartStateNode.Location, t.EndStateNode.OldLocation);
				Point offset = t.PointOffset(t.MiddlePoint, midPoint);
				t.RepositionMiddlePoint(offset);
			}

			t.InvalidateVisual();
		}

		/// <summary>
		/// MiddlePoint is a Dependency Property.
		/// </summary>
		public Point MiddlePoint
		{
			get { return (Point)GetValue(MiddlePointProperty); }
			set { SetValue(MiddlePointProperty, value); }
		}
		public static readonly DependencyProperty MiddlePointProperty =
			DependencyProperty.Register("MiddlePoint", typeof(Point), typeof(TransitionNode), new UIPropertyMetadata(new Point(), new PropertyChangedCallback(MiddlePointChanged)));

		static void MiddlePointChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			TransitionNode n = o as TransitionNode;
			n.InvalidateVisual();
			n.TransitionData.MidPoint = n.MiddlePoint;
		}

		/// <summary>
		/// Repositions the MiddlePoint based on where it was before it moved
		/// </summary>
		/// <param name="offset"></param>
		private void RepositionMiddlePoint(Point offset)
		{
			if (StartStateNode != null && EndStateNode != null)
			{
				Point mid = GetMiddlePoint(StartStateNode.Location, EndStateNode.Location);
				MiddlePoint = new Point(mid.X + offset.X, mid.Y + offset.Y);
			}
		}
		
		/// <summary>
		/// Gets the center between two given points
		/// </summary>
		/// <param name="p1"></param>
		/// <param name="p2"></param>
		/// <returns></returns>
		private Point GetMiddlePoint(Point p1, Point p2)
		{
			return new Point((p1.X + p2.X) / 2, (p1.Y + p2.Y) / 2);
		}

		/// <summary>
		/// Gets the offset between two given points
		/// </summary>
		/// <param name="p1"></param>
		/// <param name="p2"></param>
		/// <returns></returns>
		private Point PointOffset(Point p1, Point p2)
		{
			return new Point(p1.X - p2.X, p1.Y - p2.Y);
		}


		/// <summary>
		/// StartStateNode is a Dependency Property.
		/// </summary>
		public StateNode StartStateNode
		{
			get { return (StateNode)GetValue(StartStateNodeProperty); }
			set { SetValue(StartStateNodeProperty, value); }
		}
		public static readonly DependencyProperty StartStateNodeProperty =
			DependencyProperty.Register("StartStateNode", typeof(StateNode), typeof(TransitionNode), new UIPropertyMetadata(null, new PropertyChangedCallback(StartStateNodeChanged)));

		static void StartStateNodeChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			TransitionNode n = o as TransitionNode;
			n.TransitionData.StartState = n.StartStateNode.StateData;
		}

		/// <summary>
		/// EndStateNode is a Dependency Property.
		/// </summary>
		public StateNode EndStateNode
		{
			get { return (StateNode)GetValue(EndStateNodeProperty); }
			set { SetValue(EndStateNodeProperty, value); }
		}
		public static readonly DependencyProperty EndStateNodeProperty =
			DependencyProperty.Register("EndStateNode", typeof(StateNode), typeof(TransitionNode), new UIPropertyMetadata(null, new PropertyChangedCallback(EndStateNodeChanged)));

		static void EndStateNodeChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			TransitionNode n = o as TransitionNode;
			n.TransitionData.EndState = n.EndStateNode.StateData;
		}

		/// <summary>
		/// HeadHeight is a dependency property.
		/// </summary>
		public double HeadHeight
		{
			get { return (double)GetValue(HeadHeightProperty); }
			set { SetValue(HeadHeightProperty, value); }
		}
		public static readonly DependencyProperty HeadHeightProperty =
			DependencyProperty.Register("HeadHeight", typeof(double), typeof(TransitionNode), new UIPropertyMetadata(5.0));

		/// <summary>
		/// HeadWidth is a dependency property.
		/// </summary>
		public double HeadWidth
		{
			get { return (double)GetValue(HeadWidthProperty); }
			set { SetValue(HeadWidthProperty, value); }
		}
		public static readonly DependencyProperty HeadWidthProperty =
			DependencyProperty.Register("HeadWidth", typeof(double), typeof(TransitionNode), new UIPropertyMetadata(25.0));

		/// <summary>
		/// Text is a dependency property.
		/// </summary>
		public string Read
		{
			get { return (string)GetValue(ReadProperty); }
			set { SetValue(ReadProperty, value); }
		}
		public static readonly DependencyProperty ReadProperty =
			DependencyProperty.Register("Read", typeof(string), typeof(TransitionNode), new UIPropertyMetadata(TuringTab.DELTA, new PropertyChangedCallback(ReadChanged)));

		private static void ReadChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
		{
			TransitionNode t = o as TransitionNode;
			if (t.OnTextChanged != null)
			{
				t.OnTextChanged(o);
			}

			t.TransitionData.Read = t.Read == TuringTab.DELTA ? (char?)null : t.Read[0];
		}

		/// <summary>
		/// Write is a Dependency Property.
		/// </summary>
		public string Write
		{
			get { return (string)GetValue(WriteProperty); }
			set { SetValue(WriteProperty, value); }
		}
		public static readonly DependencyProperty WriteProperty =
			DependencyProperty.Register("Write", typeof(string), typeof(TransitionNode), new UIPropertyMetadata(TuringTab.DELTA, new PropertyChangedCallback(WriteChanged)));

		static void WriteChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			TransitionNode t = o as TransitionNode;
			if (t.OnTextChanged != null)
			{
				t.OnTextChanged(o);
			}

			t.TransitionData.Write = t.Write == TuringTab.DELTA ? (char?)null : t.Write[0];
		}

		/// <summary>
		/// Move is a Dependency Property.
		/// </summary>
		public TapeMovement Move
		{
			get { return (TapeMovement)GetValue(MoveProperty); }
			set { SetValue(MoveProperty, value); }
		}
		public static readonly DependencyProperty MoveProperty =
			DependencyProperty.Register("Move", typeof(TapeMovement), typeof(TransitionNode), new UIPropertyMetadata(TapeMovement.S, new PropertyChangedCallback(MoveChanged)));

		static void MoveChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			TransitionNode t = o as TransitionNode;
			if (t.OnTextChanged != null)
			{
				t.OnTextChanged(o);
			}

			t.TransitionData.Direction = t.Move;
		}

		public string Text
		{
			get
			{
				return String.Format("{0}{1}{2}", Read, Write, Move);
			}
			set
			{
				if (Validate(value))
				{
					Read = value[0].ToString();
					Write = value[1].ToString();
					Move = (TapeMovement)Enum.Parse(typeof(TapeMovement), value[2].ToString(), true);

					if (PropertyChanged != null)
						PropertyChanged(this, new PropertyChangedEventArgs("Text"));

					if (OnTextChanged != null)
					{
						OnTextChanged(this);
					}
				}
			}
		}
		

		/// <summary>
		/// ArrowBrush is a Dependency Property.
		/// </summary>
		public Brush ArrowBrush
		{
			get { return (Brush)GetValue(ArrowBrushProperty); }
			set { SetValue(ArrowBrushProperty, value); }
		}
		public static readonly DependencyProperty ArrowBrushProperty =
			DependencyProperty.Register("ArrowBrush", typeof(Brush), typeof(TransitionNode), new UIPropertyMetadata(Brushes.Green));


		/// <summary>
		/// ArrowThickness is a dependency property.
		/// </summary>
		public double ArrowThickness
		{
			get { return (double)GetValue(ArrowThicknessProperty); }
			set { SetValue(ArrowThicknessProperty, value); }
		}
		public static readonly DependencyProperty ArrowThicknessProperty =
			DependencyProperty.Register("ArrowThickness", typeof(double), typeof(TransitionNode), new UIPropertyMetadata(3.0));


		/// <summary>
		/// IsPathVisible is a dependency property.
		/// </summary>
		public bool IsPathVisible
		{
			get { return (bool)GetValue(IsPathVisibleProperty); }
			set { SetValue(IsPathVisibleProperty, value); }
		}
		public static readonly DependencyProperty IsPathVisibleProperty =
			DependencyProperty.Register("IsPathVisible", typeof(bool), typeof(TransitionNode), new UIPropertyMetadata(true, IsPathVisibleChanged));

		static void IsPathVisibleChanged(DependencyObject o, DependencyPropertyChangedEventArgs a)
		{
			TransitionNode n = ((TransitionNode)o) as TransitionNode;
			if (n.IsPathVisible)
			{
				n.curve.Visibility = Visibility.Visible;
			}
			else
			{
				n.curve.Visibility = Visibility.Hidden;
			}

			n.TransitionData.IsPathVisible = n.IsPathVisible;
		}

		/// <summary>
		/// ParentTransitionNode is a dependency property.
		/// </summary>
		public TransitionNode ParentTransitionNode
		{
			get { return (TransitionNode)GetValue(ParentTransitionNodeProperty); }
			set { SetValue(ParentTransitionNodeProperty, value); }
		}
		public static readonly DependencyProperty ParentTransitionNodeProperty =
			DependencyProperty.Register("ParentTransitionNode", typeof(TransitionNode), typeof(TransitionNode), new UIPropertyMetadata(null));

		/// <summary>
		/// ChildrenTransitionNodes is a dependency property.
		/// </summary>
		public TransitionNodeCollection ChildrenTransitionNodes
		{
			get { return (TransitionNodeCollection)GetValue(ChildrenTransitionNodesProperty); }
			set { SetValue(ChildrenTransitionNodesProperty, value); }
		}
		public static readonly DependencyProperty ChildrenTransitionNodesProperty =
			DependencyProperty.Register("ChildrenTransitionNodes", typeof(TransitionNodeCollection), typeof(TransitionNode), new UIPropertyMetadata(null));


		/// <summary>
		/// AutoCenterMiddle is a Dependency Property.
		/// </summary>
		public bool AutoCenterMiddle
		{
			get { return (bool)GetValue(AutoCenterMiddleProperty); }
			set { SetValue(AutoCenterMiddleProperty, value); }
		}
		public static readonly DependencyProperty AutoCenterMiddleProperty =
			DependencyProperty.Register("AutoCenterMiddle", typeof(bool), typeof(TransitionNode), new UIPropertyMetadata(false));


		/// <summary>
		/// Gets or sets if the Curve of the TransitionNode is being drawn
		/// </summary>
		public bool CurveVisible
		{
			get
			{
				return curve.Visibility == Visibility.Visible;
			}
			set
			{
				if (value)
				{
					curve.Visibility = Visibility.Visible;
				}
				else
				{
					curve.Visibility = Visibility.Hidden;
				}
			}
		}

		public TransitionNode() : this(true, new Transition()) { }
		public TransitionNode(Transition transitionData) : this(true, transitionData) { }
		public TransitionNode(bool autocenterMiddle) : this(autocenterMiddle, new Transition()) { }
		public TransitionNode(bool autocenterMiddle, Transition transitionData)
		{
			TransitionData = transitionData;
			
			Initialized += delegate
			{
				AutoCenterMiddle = autocenterMiddle;
				Cursor = Cursors.Hand;

				ChildrenTransitionNodes = new TransitionNodeCollection();
			};

			Loaded += delegate
			{
				if (AutoCenterMiddle)
				{
					CenterMiddlePoint();
					AutoCenterMiddle = false;
				}

				this.MouseDown += DragStart;

				ContextMenu = new ContextMenu();
				MenuItem miDelete = new MenuItem { Header = "Delete" };
				miDelete.Click += delegate { if (OnTransitionDeleteWanted != null)OnTransitionDeleteWanted(this); };
				ContextMenu.Items.Add(miDelete);

				MenuItem miHidePath = new MenuItem { Header = "Hide Path" };
				miHidePath.Click += delegate { IsPathVisible = !IsPathVisible; this.InvalidateVisual(); };
				ContextMenu.Items.Add(miHidePath);

				Renamer.Validators.Add(this);
				Renamer.DeleteClicked += delegate { if (OnTransitionDeleteWanted != null)OnTransitionDeleteWanted(this); };
			};

			OnTextChanged += delegate { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Text")); };

			InitializeComponent();
		}

		private void DragStart(object sender, MouseEventArgs args)
		{
			if (args.LeftButton == MouseButtonState.Pressed)
			{
				this.Cursor = Cursors.None;
				Mouse.AddMouseMoveHandler(Application.Current.MainWindow, DragMove);
				Mouse.AddMouseUpHandler(Application.Current.MainWindow, DragEnd);

				this.CaptureMouse();
			}
		}

		private void DragMove(object sender, MouseEventArgs args)
		{
			MiddlePoint = new Point(args.MouseDevice.GetPosition(this).X, args.MouseDevice.GetPosition(this).Y);
		}

		private void DragEnd(object sender, MouseEventArgs args)
		{
			this.Cursor = Cursors.Hand;
			CancelDrag();
		}

		public void CancelDrag()
		{
			this.ReleaseMouseCapture();
			Mouse.RemoveMouseMoveHandler(Application.Current.MainWindow, DragMove);
			Mouse.RemoveMouseMoveHandler(Application.Current.MainWindow, DragEnd);
			this.InvalidateVisual();
			//DragEnd(this, null);
		}
		
		public void CenterMiddlePoint()
		{
			if (StartStateNode != null && EndStateNode != null)
			{
				Point mid = GetMiddlePoint(StartStateNode.Location, EndStateNode.Location);
				MiddlePoint = new Point(mid.X, mid.Y);
			}
		}

		public Point CalcPoint(double t)
		{
			double startMulti = Math.Pow(1 - t, 2);
			double midMulti = (2 * (1 - t) * t);
			double endMulti = Math.Pow(t, 2);
			Point start = new Point(StartPoint.X * startMulti, StartPoint.Y * startMulti);
			Point mid = new Point(quadraticBezierSegment.Point1.X * midMulti, quadraticBezierSegment.Point1.Y * midMulti);
			Point end = new Point(quadraticBezierSegment.Point2.X * endMulti, quadraticBezierSegment.Point2.Y * endMulti);
			return new Point(start.X + mid.X + end.X, start.Y + mid.Y + end.Y);
		}

		public static Point CalcMidPoint(Point startPoint, Point midPoint, Point endPoint, double t)
		{
			double startMulti = Math.Pow(1 - t, 2);
			double midMulti = 1 / (2 * (1 - t) * t);
			double endMulti = Math.Pow(t, 2);
			Point start = new Point(startPoint.X * startMulti, startPoint.Y * startMulti);
			Point end = new Point(endPoint.X * endMulti, endPoint.Y * endMulti);
			return new Point(midMulti * (midPoint.X - start.X - end.X), midMulti * (midPoint.Y - start.Y - end.Y));
		}

		protected override void OnRender(DrawingContext dc)
		{
			base.OnRender(dc);

			Point offset = PointOffset(EndPoint, MiddlePoint);
			offset.X = Math.Abs(offset.X);
			offset.Y = Math.Abs(offset.Y);

			double t = .9;
			double x1 = this.CalcPoint(t).X;
			double y1 = this.CalcPoint(t).Y;

			double x2 = quadraticBezierSegment.Point2.X;
			double y2 = quadraticBezierSegment.Point2.Y;

			double theta = Math.Atan2(y1 - y2, x1 - x2);
			double sint = Math.Sin(theta);
			double cost = Math.Cos(theta);

			Point pt1 = new Point(
				x2 + (1 * cost - 0 * sint),
				y2 + (1 * sint + 0 * cost));

			Point pt3 = new Point(
				x2 + (HeadWidth * cost - HeadHeight * sint),
				y2 + (HeadWidth * sint + HeadHeight * cost));

			Point pt4 = new Point(
				x2 + (HeadWidth * cost + HeadHeight * sint),
				y2 - (HeadHeight * cost - HeadWidth * sint));

			if (IsPathVisible)
			{
				dc.DrawLine(new Pen(ArrowBrush, ArrowThickness), pt1, pt3);
				dc.DrawLine(new Pen(ArrowBrush, ArrowThickness), pt1, pt4);
				//dc.DrawLine(new Pen(Brushes.Black, 5), pt3, pt4);
			}
		}

		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion

		#region IRenameValidator Members

		public bool Validate(string text)
		{
			if (text.Length != 3)
				throw new Exception("The transition must have a length of 3");
			else if (!Enum.GetNames(typeof(TapeMovement)).Select(str => str.ToUpper()).Contains(text[2].ToString().ToUpper()))
				throw new Exception("The transition's 3rd character must be 'R', 'L', or 'S'");
			return true;
		}

		#endregion
	}
}
